

#include "Engine3d.h"
#include "PixelCoords.h"
#include "Math.h"

#include "Profiler.h"

#include <iostream>
#include <algorithm>

using namespace Iride;

Engine3d::Engine3d(IPixelDrawer* pDrawer)
:mpDrawer(pDrawer), mWireFrame(false), mWinding(WIND_CCW), mWidth(pDrawer->GetWidth()), mHeight(pDrawer->GetHeight())
{
	mDepthBuffer  = new Real [mWidth * mHeight];

	Uint maxDim = Math::Max(mWidth, mHeight);

	mBufferX1 = new int [mHeight];
	mBufferX2 = new int [mHeight];

	mBufferY1 = new int [mWidth];

	mInvZBuf1 = new Real[mHeight];
	mInvZBuf2 = new Real[mHeight];
	mInvZBufAtt = new Real[maxDim];

	mDepthBuf1 = new Real[mHeight];
	mDepthBuf2 = new Real[mHeight];
	mDepthBufSl = new Real[mWidth];

	mColorBuf1 = new Color [mHeight];
	mColorBuf2 = new Color [mHeight];
	mColorBufSl = new Color [mWidth];
}

Engine3d::~Engine3d()
{
	delete [] mDepthBuffer;

	delete [] mBufferX1;
	delete [] mBufferX2;

	delete [] mBufferY1;

	delete [] mInvZBuf1;
	delete [] mInvZBuf2;
	delete [] mInvZBufAtt;

	delete [] mDepthBuf1;
	delete [] mDepthBuf2;
	delete [] mDepthBufSl;

	delete [] mColorBuf1;
	delete [] mColorBuf2;
	delete [] mColorBufSl;

}

bool AlwaysTrue(const Vector3 &pointA, const Vector3 &pointB, const Vector3 &pointC)
{
	return true;
}

bool CheckCw(const Vector3 &pointA, const Vector3 &pointB, const Vector3 &pointC)
{
	Vector3 temp = Vector3(1,0,0).Cross(Vector3(0,1,0));

	Vector3 vec1 = pointB - pointA;
	Vector3 vec2 = pointC - pointA;
	Vector3 cross = vec2.Cross(vec1);

	return cross.Dot(Vector3(0,0,1)) > 0;
}

bool CheckCcw(const Vector3 &pointA, const Vector3 &pointB, const Vector3 &pointC)
{
	Vector3 vec1 = pointB - pointA;
	Vector3 vec2 = pointC - pointA;
	Vector3 cross = vec2.Cross(vec1);

	return cross.Dot(Vector3(0,0,1)) < 0;
}


void Engine3d::BeginRender()
{
	Uint dim = mWidth * mHeight;
	for(Uint i = 0; i < dim; i++)
	{
		mDepthBuffer[i] = 1.0f;
	}

	switch(mWinding)
	{
		case WIND_CW:
			mTestWinding = &CheckCw;
			break;
		case WIND_CCW:
			mTestWinding = &CheckCcw;
			break;
		default:
			mTestWinding = &AlwaysTrue;
			break;

	}
}

void getYSortedIndices( PixelCoords coords[], int res[] )
{
	if ( coords[0].y  > coords[1].y )
	{
		if ( coords[0].y > coords[2].y)
		{
			if ( coords[1].y > coords[2].y )
			{
				res[0] = 0;
				res[1] = 1;
				res[2] = 2;
			}
			else
			{
				res[0] = 0;
				res[1] = 2;
				res[2] = 1;
			}
		}
		else
		{	// [2].y >= [0].y > [1].y
			res[0] = 2;
			res[1] = 0;
			res[2] = 1;
		}
	}
	else
	{		// [1].y >= [0].y > [2].y
		if ( coords[0].y > coords[2].y)
		{
			res[0] = 1;
			res[1] = 0;
			res[2] = 2;
		}
		else
		{
			// [1].y >= [2].y >= [0].y
			if ( coords[1].y > coords[2].y )
			{
				res[0] = 1;
				res[1] = 2;
				res[2] = 0;
			}
			else
			{
				// [2].y >= [1].y >= [0].y
				res[0] = 2;
				res[1] = 1;
				res[2] = 0;
			}
		}
	}
}

void Engine3d::LerpColors(Color colorA, Color colorB, Real startZ, Real endZ, int numColors, Color * pLerpColors)
{
	Real invStartZ = 1.0f / startZ;
	Real invEndZ = 1.0f / endZ;

	Math::Lerp(invStartZ, invEndZ, numColors, mInvZBufAtt);

	LerpColors(colorA, colorB, numColors, mInvZBufAtt, pLerpColors);
}

void Engine3d::LerpColors(Color colorA, Color colorB, int numColors, Real* invZs, Color * pLerpColors)
{
	if (numColors > 1)
	{
		Real invStartZ = invZs[0];
		Real invEndZ = invZs[numColors-1];

		Real step = 1.0 / Real(numColors - 1);

		Real rStep = (Real(colorB.r) * invEndZ - Real(colorA.r) * invStartZ) * step;
		Real gStep = (Real(colorB.g) * invEndZ - Real(colorA.g) * invStartZ) * step;
		Real bStep = (Real(colorB.b) * invEndZ - Real(colorA.b) * invStartZ) * step;
		Real aStep = (Real(colorB.a) * invEndZ - Real(colorA.a) * invStartZ) * step;

		Real rAcc = Real(colorA.r) * invStartZ;
		Real gAcc = Real(colorA.g) * invStartZ;
		Real bAcc = Real(colorA.b) * invStartZ;
		Real aAcc = Real(colorA.a) * invStartZ;

		pLerpColors[0] = colorA;

		int numLoops = numColors - 1;

		for (int i = 1; i < numLoops; i++)
		{
			rAcc += rStep;
			gAcc += gStep;
			bAcc += bStep;
			aAcc += aStep;

			pLerpColors[i] = Color( rAcc / invZs[i],gAcc / invZs[i], bAcc / invZs[i], aAcc / invZs[i]);
		}

		pLerpColors[numColors-1] = colorB;
	}
	else
	{
		if (numColors == 1)
		{
			pLerpColors[0].r = colorA.r * 0.5 + colorB.r * 0.5;
			pLerpColors[0].g = colorA.g * 0.5 + colorB.g * 0.5;
			pLerpColors[0].b = colorA.b * 0.5 + colorB.b * 0.5;
			pLerpColors[0].a = colorA.a * 0.5 + colorB.a * 0.5;
		}
	}
}

void Engine3d::LerpAttribute(Real startAtt, Real endAtt, Uint numValues, Real* invZs, Real* out)
{
	Real invStartZ = invZs[0];
	Real invEndZ = invZs[numValues-1];

	Math::Lerp(startAtt * invStartZ, endAtt * invEndZ, numValues, out);

	for (Uint i = 0; i < numValues; i++)
	{
		out[i] = out[i] / invZs[i];
	}
}

void Engine3d::LerpAttribute(Real startAtt, Real endAtt, Real startZ, Real endZ, Uint numValues, Real* out)
{
	Real invStartZ = 1.0f / startZ;
	Real invEndZ = 1.0f / endZ;

	Math::Lerp(invStartZ, invEndZ, numValues, mInvZBufAtt);

	LerpAttribute(startAtt,endAtt, numValues, mInvZBufAtt, out);
}

void Engine3d::SetFrustum (Real left, Real right, Real bottom, Real top, Real near, Real far)
{
	mFrustrum[0][0] = (2.0 * near) / (right - left);
	mFrustrum[0][1] = 0.0;
	mFrustrum[0][2] = (right + left) / (right - left);
	mFrustrum[0][3] = 0.0;

	mFrustrum[1][0] = 0.0;
	mFrustrum[1][1] = (2.0 * near) / (top - bottom);
	mFrustrum[1][2] = (top + bottom) / (top - bottom);
	mFrustrum[1][3] = 0.0;

	mFrustrum[2][0] = 0.0;
	mFrustrum[2][1] = 0.0;
	mFrustrum[2][2] = - (far + near) / (far - near);
	mFrustrum[2][3] = - (2.0 * near * far) / (far - near);

	mFrustrum[3][0] = 0.0;
	mFrustrum[3][1] = 0.0;
	mFrustrum[3][2] = - 1.0;
	mFrustrum[3][3] = 0.0;
}

bool Engine3d::IsInsideScreen(const Vector4 &vec)
{
	return (vec.x >=0.0f && vec.x < mpDrawer->GetWidth() && vec.y >=0.0f && vec.y <= mpDrawer->GetHeight());
}

bool IsInsideClipSpace(const Vector4 &clipVecA, const Vector4 &clipVecB, const Vector4 &clipVecC)
{
		// All vertices must be between near and far
	if (clipVecA.z >= -1.0f && clipVecA.z <= 1.0f && clipVecB.z >= -1.0f && clipVecB.z <= 1.0f && clipVecC.z >= -1.0f && clipVecC.z <= 1.0f)
	{
			// At least one vertex must be between left and right, and between top and botton
		if ( (clipVecA.x >= -1.0f && clipVecA.x <= 1.0f && clipVecA.y >= -1.0f && clipVecA.y <= 1.0f)
			|| (clipVecB.x >= -1.0f && clipVecB.x <= 1.0f && clipVecB.y >= -1.0f && clipVecB.y <= 1.0f)
			|| (clipVecC.x >= -1.0f && clipVecC.x <= 1.0f && clipVecC.y >= -1.0f && clipVecC.y <= 1.0f)
			)
			{
				return true;
			}
			else
			{
				return false;
			}
	}
	else
	{
		return false;
	}
}

void Engine3d::Draw(Vector3 pointA, Vector3 pointB, Vector3 pointC, Color colorA, Color colorB, Color colorC)
{
	if ( mTestWinding(pointA, pointB, pointC) )
	{
		Real halfWidth = mpDrawer->GetWidth() / 2;
		Real halfHeight = mpDrawer->GetHeight() / 2;

		Vector4 clipVecA = mFrustrum * Vector4(pointA);
		Vector4 clipVecB = mFrustrum * Vector4(pointB);
		Vector4 clipVecC = mFrustrum * Vector4(pointC);

		clipVecA.x /= clipVecA.w;
		clipVecA.y /= clipVecA.w;
		clipVecA.z /= clipVecA.w;
		clipVecA.w  = -clipVecA.w;

		clipVecB.x /= clipVecB.w;
		clipVecB.y /= clipVecB.w;
		clipVecB.z /= clipVecB.w;
		clipVecB.w  = -clipVecB.w;

		clipVecC.x /= clipVecC.w;
		clipVecC.y /= clipVecC.w;
		clipVecC.z /= clipVecC.w;
		clipVecC.w  = -clipVecC.w;


		if (IsInsideClipSpace(clipVecA, clipVecB, clipVecC))
		{
			Vector4 screenVecA( ( clipVecA.x + 1.0) * halfWidth,
								( clipVecA.y + 1.0) * halfHeight,
								  clipVecA.z,
								  clipVecA.w );
			Vector4 screenVecB( ( clipVecB.x + 1.0) * halfWidth,
								( clipVecB.y + 1.0) * halfHeight,
								  clipVecB.z,
								  clipVecB.w );
			Vector4 screenVecC( ( clipVecC.x + 1.0) * halfWidth,
								( clipVecC.y + 1.0) * halfHeight,
								  clipVecC.z,
								  clipVecC.w );

			RasterTriangle(screenVecA, screenVecB, screenVecC, colorA, colorB, colorC);
		}
	}
}

void Engine3d::Draw(Vector3 * vertices, Uint* indices, Color* colors, Uint numTriangles)
{
	Uint i = 0;
	Uint numIndices = numTriangles * 3;
	while (i < numIndices)
	{
//		std::cout << "Drawing (" << vertices[indices[i]].x << "," << vertices[indices[i]].y << "," << vertices[indices[i]].z << ") ("
//		<< vertices[indices[i+1]].x << "," << vertices[indices[i+1]].y << "," << vertices[indices[i+1]].z << ") ("
//		<< vertices[indices[i+2]].x << "," << vertices[indices[i+2]].y << "," << vertices[indices[i+2]].z << ")" << std::endl;

		Draw( vertices[indices[i]], vertices[indices[i+1]], vertices[indices[i+2]], colors[indices[i]], colors[indices[i+1]], colors[indices[i+2]]);
		i += 3;
	}
}

void Engine3d::RasterLine(Uint x1, Uint y1, Uint x2, Uint y2, Color color1, Color color2, Real originalZ1, Real originalZ2)
{
	int lastX = mpDrawer->GetWidth()-1;
	int lastY = mpDrawer->GetHeight()-1;

	Uint xLength = Math::Abs(int(x2 - x1));
	Uint yLength = Math::Abs(int(y2 - y1));

	Uint length;

	if (xLength > yLength)
	{
		if (x1 > x2)
		{
			std::swap(x1, x2);
			std::swap(y1, y2);
			color1.Swap(color2);
		}
		length = x2 - x1;

		for (Uint i=0; i < length; i++) { mBufferX1[i] = x1 + i; }
		Math::GetLine(x1, y1, x2, y2, mBufferY1);
	}
	else
	{
		if (y1 > y2)
		{
			std::swap(x1, x2);
			std::swap(y1, y2);
			color1.Swap(color2);
		}
		length = y2 - y1;

		for (Uint i=0; i < length; i++) { mBufferY1[i] = y1 + i; }
		Math::GetLine(y1, x1, y2, x2, mBufferX1);
	}

	LerpColors(color1, color2, originalZ1, originalZ2, length, mColorBufSl);

	int currX;
	int currY;

	for(Uint i = 0; i < length; i++)
	{
		//std::cout << "Drawing x " << (x1 + i) << " y " << mBufferY1[i] << std::endl;

		currX = mBufferX1[i];
		currY = mBufferY1[i];

		if (currX >= 0 && currX <= lastX && currY >= 0 && currY <= lastY)
		{
			mpDrawer->DrawPixel(currX, currY, mColorBufSl[i].r * 255, mColorBufSl[i].g * 255, mColorBufSl[i].b * 255);
		}
	}

}

void Engine3d::RasterScanLine(Uint x1, Uint x2, Uint y, Real depth1, Real depth2, Color color1, Color color2, Real originalZ1, Real originalZ2)
{
	Uint length;

	if (x2 < x1)
	{
		std::swap(x1,x2);
		std::swap(depth1,depth2);
		std::swap(originalZ1,originalZ2);
		color1.Swap(color2);
	}

	length = x2 - x1;

	Real invStartZ = 1.0f / originalZ1;
	Real invEndZ = 1.0f / originalZ2;

	Math::Lerp(invStartZ, invEndZ, length, mInvZBufAtt);

	LerpAttribute(depth1, depth2, length, mInvZBufAtt, mDepthBufSl);
	LerpColors(color1, color2, length, mInvZBufAtt, mColorBufSl);

	Byte red, green, blue;

	for (Uint i = 0; i < length; i++)
	{
		if (mDepthBufSl[i] < mDepthBuffer[mWidth * y + x1 + i])
		{
			if (mDepthBuffer[mWidth * y + x1 + i] != 1.0f)
			{
				//std::cout << "Azz! New val: " << mDepthBufSl[i] << " Old val: " << mDepthBuffer[mWidth * y + x1 + i] << std::endl;
			}

			red = (mColorBufSl[i].r * 255.0);
			green = (mColorBufSl[i].g * 255.0);
			blue = (mColorBufSl[i].b * 255.0);

			mpDrawer->DrawPixel(x1 + i, y, red, green, blue);
			mDepthBuffer[mWidth * y + x1 + i] = mDepthBufSl[i];
		}
	}
}

void Engine3d::RasterTriangle(Vector4 pointA, Vector4 pointB, Vector4 pointC, Color colorA, Color colorB, Color colorC)
{
	Profiler profiler;

	if (mWireFrame)
	{
		profiler.resetTimer();

		RasterLine(pointA.x, pointA.y, pointB.x, pointB.y, colorA, colorB, pointA.w, pointB.w);
		RasterLine(pointA.x, pointA.y, pointC.x, pointC.y, colorA, colorC, pointA.w, pointC.w);
		RasterLine(pointC.x, pointC.y, pointB.x, pointB.y, colorC, colorB, pointC.w, pointB.w);

		#ifdef PROFILE_TRIANGLE_RENDERING
		profiler.printElapsedClock("3 RasterLine: ");
		#endif
	}
	else
	{
		int ind [3];
		PixelCoords coords [] = { PixelCoords(pointA.x, pointA.y), PixelCoords(pointB.x, pointB.y), PixelCoords(pointC.x, pointC.y)  };

		Real depths [] = { pointA.z, pointB.z, pointC.z};

		Real origZs [] = { pointA.w, pointB.w, pointC.w};

		Color* colors [] = { &colorA, &colorB, &colorC };

		getYSortedIndices(coords, ind);

		PixelCoords maxPC = coords[ind[0]];
		PixelCoords midPC = coords[ind[1]];
		PixelCoords minPC = coords[ind[2]];

		int shortLength =  maxPC.y - midPC.y;

		profiler.resetTimer();

		Math::GetLine(maxPC.y, maxPC.x, midPC.y, midPC.x, mBufferX1);
		Math::GetLine(midPC.y, midPC.x, minPC.y, minPC.x, (mBufferX1 + shortLength) );
		Math::GetLine(maxPC.y, maxPC.x, minPC.y, minPC.x, mBufferX2);

		#ifdef PROFILE_TRIANGLE_RENDERING
		profiler.printElapsedClock("3 GetLine: ");
		#endif

		int numPoints = maxPC.y - minPC.y;

		profiler.resetTimer();

		LerpColors( *(colors[ind[0]]), *(colors[ind[1]]), origZs[ind[0]], origZs[ind[1]], shortLength, mColorBuf1 );
		LerpColors( *(colors[ind[1]]), *(colors[ind[2]]), origZs[ind[1]], origZs[ind[2]], numPoints - shortLength, mColorBuf1 + shortLength);
		LerpColors( *(colors[ind[0]]), *(colors[ind[2]]), origZs[ind[0]], origZs[ind[2]], numPoints, mColorBuf2);

		#ifdef PROFILE_TRIANGLE_RENDERING
		profiler.printElapsedClock("3 Lerp: ");
		#endif

		Math::Lerp( 1.0f / origZs[ind[0]], 1.0f / origZs[ind[1]], shortLength, mInvZBuf1);
		Math::Lerp( 1.0f / origZs[ind[1]], 1.0f / origZs[ind[2]], numPoints - shortLength, mInvZBuf1 + shortLength);
		Math::Lerp( 1.0f / origZs[ind[0]], 1.0f / origZs[ind[2]], numPoints, mInvZBuf2);

		LerpAttribute( depths[ind[0]], depths[ind[1]], shortLength, mInvZBuf1, mDepthBuf1 );
		LerpAttribute( depths[ind[1]], depths[ind[2]], numPoints - shortLength, mInvZBuf1 + shortLength, mDepthBuf1 + shortLength );
		LerpAttribute( depths[ind[0]], depths[ind[2]], numPoints, mInvZBuf2, mDepthBuf2 );

		Uint lastX = mpDrawer->GetWidth() - 1;
		Uint lastY = mpDrawer->GetHeight() - 1;
		Uint x1;
		Uint x2;
		Uint y;
		Real depth1;
		Real depth2;


		profiler.resetTimer();

		for(int j = 0; j < numPoints; j++)
		{
			// std::cout << "Drawing x1 " << mBufferX1[j] << " x2 " << mBufferX2[j] << " y " <<  maxPC.y - j << std::endl;

			x1 = Math::Clamp(mBufferX1[j], 0, lastX);
			x2 = Math::Clamp(mBufferX2[j], 0, lastX);
			y = maxPC.y - j;

			depth1 = mDepthBuf1[j];
			depth2 = mDepthBuf2[j];

			if ( 0 <= y && y <= lastY)
			{
				RasterScanLine(x1, x2, y, depth1, depth2, mColorBuf1[j], mColorBuf2[j], 1.0f / mInvZBuf1[j] , 1.0f / mInvZBuf2[j]);
			}

		}

		#ifdef PROFILE_TRIANGLE_RENDERING
		profiler.printElapsedClock("RasterScanLines: ");
		#endif
	}
}


